//
//  MMSqliteModelTool.m
//  sqlite封装
//
//  Created by lianglibao on 2018/1/15.
//  Copyright © 2018年 梁立保. All rights reserved.
//

#import "MMSqliteModelTool.h"
#import "MMModelTool.h"
#import "MMSqliteTool.h"
#import "MMTableTool.h"

@implementation MMSqliteModelTool
// 关于这个工具类的封装
// 使用runtime动态获取
+ (void)createTable:(Class)cls
                uid:(NSString *)uid
       successBlock:(void(^)(BOOL isSuccess))successBlock {
    // 1. 创建表格的sql语句给拼接出来
    // 尽可能多的, 能够自己获取, 就自己获取, 实在判定不了用的意图的, 只能让用户来告诉我们
    
    // create table if not exists 表名(字段1 字段1类型, 字段2 字段2类型 (约束),...., primary key(字段))
    // 1.1 获取表格名称
    NSString *tableName = [MMModelTool tableName:cls];
    
    if (![cls respondsToSelector:@selector(primaryKey)]) {
        NSLog(@"如果想要操作这个模型, 必须要实现+ (NSString *)primaryKey;这个方法, 来告诉我主键信息");
        if (successBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(NO);
            });
        }
        return;
    }
    
    dispatch_async(MMSqliteConcurrentQueue(), ^{
        NSString *primaryKey = [cls primaryKey];
        // 1.2 获取一个模型里面所有的字段, 以及类型
        // 建表方式1:
        // NSString *createTableSql = [NSString stringWithFormat:@"create table if not exists %@(%@, primary key(%@))", tableName, [MMModelTool columnNamesAndTypesStr:cls], primaryKey];
        
        //建表方式2:
        // NSString *sql = @"create table if not exists t_stu(id integer primary key autoincrement, name text not null, age integer, score real)";
        NSString *createTableSql = [NSString stringWithFormat:@"create table if not exists %@(%@, %@ primary key autoincrement)", tableName, [MMModelTool columnNamesAndTypesStr:cls], primaryKey];
        
        // 2. 执行
        BOOL result = [MMSqliteTool deal:createTableSql uid:uid];
        if (successBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(result);
            });
        }
    });
}

+ (void)isTableRequiredUpdate:(Class)cls
                          uid:(NSString *)uid
                 successBlock:(void(^)(BOOL isSuccess))successBlock {
    dispatch_async(MMSqliteConcurrentQueue(), ^{
        NSArray *modelNames = [MMModelTool allTableSortedIvarNames:cls];
        NSArray *tableNames = [MMTableTool tableSortedColumnNames:cls uid:uid];
        if (modelNames.count != tableNames.count) {
            if (successBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    successBlock(NO);
                });
            }
            return;
        }
        
        BOOL isEqual = NO;
        for (NSString *propertyName in tableNames) {
            if ([modelNames containsObject:propertyName]) {
                isEqual = YES;
            } else {
                isEqual = NO;
                break;
            }
        }
        if (successBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(isEqual);
            });
        }
    });
}

+ (void)updateTable:(Class)cls
                uid:(NSString *)uid
       successBlock:(void(^)(BOOL isSuccess))successBlock {
    if (!cls) {
        NSLog(@"传入的不是正确的Class");
        if (successBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(NO);
            });
        }
        return;
    }
    
    // 1. 创建一个拥有正确结构的临时表
    // 1.1 获取表格名称
    NSString *tmpTableName = [MMModelTool tmpTableName:cls];
    NSString *tableName = [MMModelTool tableName:cls];
    
    __block BOOL result = YES;
    if (![cls respondsToSelector:@selector(primaryKey)]) {
        NSLog(@"如果想要操作这个模型, 必须要实现+ (NSString *)primaryKey;这个方法, 来告诉我主键信息");
        result = NO;
    } else if ([tmpTableName isEqualToString:tableName]) {
        NSLog(@"需要修改的表名与原表名一致,不处理!!");
        result = NO;
    }
    
    if (!result) {
        if (successBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(NO);
            });
        }
        return;
    }
    
    dispatch_async(MMSqliteConcurrentQueue(), ^{
        // 1.2担心主键会被改变,获取一下老的主键名.
        NSString *oldPrimaryKey = [self fetchPrimaryKey:cls uid:uid];
        
        NSMutableArray *execSqls = [NSMutableArray array];
        NSString *newPrimaryKey = [cls primaryKey];
        NSString *createTableSql = [NSString stringWithFormat:@"create table if not exists %@(%@, primary key(%@));", tmpTableName, [MMModelTool columnNamesAndTypesStr:cls], newPrimaryKey];
        [execSqls addObject:createTableSql];
        
        // 2. 根据主键, 插入数据
        // insert into MMstu_tmp(stuNum) select stuNum from MMstu;
        NSString *insertPrimaryKeyData = [NSString stringWithFormat:@"insert into %@(%@) select %@ from %@;", tmpTableName, newPrimaryKey, oldPrimaryKey, tableName];
        [execSqls addObject:insertPrimaryKeyData];
        
        // 3. 根据主键, 把所有的数据更新到新表里面
        NSArray *oldNames = [MMTableTool tableSortedColumnNames:cls uid:uid];
        NSArray *newNames = [MMModelTool allTableSortedIvarNames:cls];
        
        for (NSString *columnName in newNames) {
            if (![oldNames containsObject:columnName]) {
                continue;
            }
            NSString *updateSql = [NSString stringWithFormat:@"update %@ set %@ = (select %@ from %@ where %@.%@ = %@.%@)", tmpTableName, columnName, columnName, tableName, tmpTableName, newPrimaryKey, tableName, oldPrimaryKey];
            [execSqls addObject:updateSql];
        }
        
        NSString *deleteOldTable = [NSString stringWithFormat:@"drop table if exists %@", tableName];
        [execSqls addObject:deleteOldTable];
        
        NSString *renameTableName = [NSString stringWithFormat:@"alter table %@ rename to %@", tmpTableName, tableName];
        [execSqls addObject:renameTableName];
        
        result = [MMSqliteTool dealSqls:execSqls uid:uid];
        if (successBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(result);
            });
        }
    });
}

+ (void)saveModel:(id)model
          withuid:(NSString *)uid
     successBlock:(void(^)(BOOL isSuccess))successBlock {
    // 0. 验证表格是否存在
    [self createTable:[model class]
                  uid:uid
         successBlock:^(BOOL isSuccess) {
        if (!isSuccess) {
            if (successBlock) {
                successBlock(NO);
            }
        } else {
            // 1. 获取表名称, 主键
            NSString *tableName = NSStringFromClass([model class]);
            NSString *primaryKey = [[model class] primaryKey];
            dispatch_block_t targetBlock = ^{
                // 1. 判断是否已经存在该记录, 如果是, 则更新, 如果不是, 则插入
                NSString *hasSql = [NSString stringWithFormat:@"select * from %@ where %@ = '%@'", tableName, primaryKey, [(NSObject *)model valueForKeyPath:primaryKey]];
                NSArray *result = [MMSqliteTool querySql:hasSql uid:uid];
                NSArray *columnNames = [MMModelTool classIvarNameTypeDic:[model class]].allKeys;
                if (result.count > 0) {
                    // 执行更新操作
                    // "update tableName set columnName = 'value',columnName = 'value' where primarykey = 'primaryKeyValue'";
                    
                    NSMutableArray *setValueArray = [NSMutableArray array];
                    for (NSString *columnName in columnNames) {
                        
                        // 获取value
                        id value = [(NSObject *)model valueForKeyPath:columnName];
                        
                        
                        if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) {
                            NSData *data = [NSJSONSerialization dataWithJSONObject:value options:NSJSONWritingPrettyPrinted error:nil];
                            value = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                        }
                        
                        NSString *str = [NSString stringWithFormat:@"%@ = '%@'", columnName, value];
                        [setValueArray addObject:str];
                        
                    }
                    
                    NSString *updateSql = [NSString stringWithFormat:@"update %@ set %@ where %@ = '%@'", tableName, [setValueArray componentsJoinedByString:@","], primaryKey, [(NSObject *)model valueForKeyPath:primaryKey]];
                    BOOL result = [MMSqliteTool deal:updateSql uid:uid];
                    if (successBlock) {
                        dispatch_async(dispatch_get_main_queue(), ^{
                            successBlock(result);
                        });
                    }
                } else {
                    // 执行插入操作
                    // insert into tableName(columnName, columnName) values (value, value)
                    NSMutableArray *valueArray = [NSMutableArray array];
                    for (NSString *columnName in columnNames) {
                        
                        id value = [(NSObject *)model valueForKeyPath:columnName];
                        
                        if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) {
                            NSData *data = [NSJSONSerialization dataWithJSONObject:value options:NSJSONWritingPrettyPrinted error:nil];
                            value = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                        }
                        
                        [valueArray addObject:[NSString stringWithFormat:@"'%@'", value]];
                    }
                    
                    NSString *insertSql = [NSString stringWithFormat:@"insert into %@(%@) values (%@)", tableName, [columnNames componentsJoinedByString:@","], [valueArray componentsJoinedByString:@","]];
                    BOOL result = [MMSqliteTool deal:insertSql uid:uid];
                    if (successBlock) {
                        dispatch_async(dispatch_get_main_queue(), ^{
                            successBlock(result);
                        });
                    }
                }
            };
            
            // 1.1 担心主键会被改变,获取一下老的主键名.
            // 如果主键改变就先更新表, 更新表成功就继续, 未成功就返回NO
            NSString *oldPrimaryKey = [self fetchPrimaryKey:[model class] uid:uid];
            if (![oldPrimaryKey isEqualToString:primaryKey]) {
                [self updateTable:[model class]
                              uid:uid
                     successBlock:^(BOOL isSuccess) {
                    if (!isSuccess) {
                        if (successBlock) {
                            successBlock(NO);
                        }
                    } else {
                        dispatch_async(MMSqliteConcurrentQueue(), targetBlock);
                    }
                }];
            } else {
                dispatch_async(MMSqliteConcurrentQueue(), targetBlock);
            }
        }
    }];
}

+ (void)deleteModel:(id)model
            withuid:(NSString *)uid
       successBlock:(void(^)(BOOL isSuccess))successBlock {
    NSString *primaryKey = [[model class] primaryKey];
    NSString *primaryKeyValue = [(NSObject *)model valueForKey:primaryKey];
    [self deleteModels:[model class]
       whereColumnName:primaryKey
               isValue:primaryKeyValue
               withuid:uid
          successBlock:successBlock];
}

+ (void)deleteModels:(Class)modelClass
     whereColumnName:(NSString *)column
             isValue:(id)value
             withuid:(NSString *)uid
        successBlock:(void(^)(BOOL isSuccess))successBlock {
    // 验证是否有主键
    NSString *primaryKey = [modelClass primaryKey];
    if (primaryKey.length == 0) {
        if (successBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(NO);
            });
        }
        return;
    }
    
    dispatch_block_t targetBlock = ^{
        NSString *tableName = NSStringFromClass(modelClass);
        NSString *deleteSql = [NSString stringWithFormat:@"delete from %@ where %@ = '%@'", tableName, column, value];
        BOOL result = [MMSqliteTool deal:deleteSql uid:uid];
        if (successBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(result);
            });
        }
    };
    
    // 1.1 担心主键会被改变,获取一下老的主键名.
    NSString *oldPrimaryKey = [self fetchPrimaryKey:modelClass uid:uid];
    if (![oldPrimaryKey isEqualToString:primaryKey]) {
        // 如果主键改变就先更新表, 更新表成功就继续, 未成功就返回NO
        [self updateTable:modelClass
                      uid:uid
             successBlock:^(BOOL isSuccess) {
            if (!isSuccess) {
                if (successBlock) {
                    successBlock(NO);
                }
            } else {
                dispatch_async(MMSqliteConcurrentQueue(), targetBlock);
            }
        }];
    } else {
        dispatch_async(MMSqliteConcurrentQueue(), targetBlock);
    }
}

+ (void)deleteModels:(Class)modelClass
       withCondition:(NSString *)whereSQL
             withuid:(NSString *)uid
        successBlock:(void(^)(BOOL isSuccess))successBlock {
    // 验证是否有主键
    NSString *primaryKey = [modelClass primaryKey];
    if (primaryKey.length == 0) {
        if (successBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(NO);
            });
        }
        return;
    }
    
    dispatch_block_t targetBlock = ^{
        NSString *tableName = NSStringFromClass(modelClass);
        NSString *deleteSql = [NSString stringWithFormat:@"delete from %@", tableName];
        if (whereSQL.length != 0) {
            deleteSql = [deleteSql stringByAppendingString:[NSString stringWithFormat:@" where %@",  whereSQL]];
        }
        
        BOOL result = [MMSqliteTool deal:deleteSql uid:uid];
        if (successBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                successBlock(result);
            });
        }
    };
    
    // 1.1 担心主键会被改变,获取一下老的主键名.
    // 如果主键改变就先更新表, 更新表成功就继续, 未成功就返回NO
    NSString *oldPrimaryKey = [self fetchPrimaryKey:modelClass uid:uid];
    if (![oldPrimaryKey isEqualToString:primaryKey]) {
        [self updateTable:modelClass uid:uid successBlock:^(BOOL isSuccess) {
            if (!isSuccess) {
                if (successBlock) {
                    successBlock(NO);
                }
            } else {
                dispatch_async(MMSqliteConcurrentQueue(), targetBlock);
            }
        }];
    } else {
        dispatch_async(MMSqliteConcurrentQueue(), targetBlock);
    }
}

//MARK: - 获取已有数据库表的主键名称
+ (NSString *)fetchPrimaryKey:(Class)cls
                          uid:(NSString *)uid {
    NSMutableArray *tableInfo = [MMSqliteTool querySql:[NSString stringWithFormat:@"pragma table_info(%@)", [MMModelTool tableName:cls]] uid:uid];
    // 担心主键会被改变,获取一下老的主键名.
    __block NSString *oldPrimaryKey = @"";
    [tableInfo enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj[@"pk"] intValue] == 1) {
            oldPrimaryKey = obj[@"name"];
            *stop = YES;
            return;
        }
    }];
    return oldPrimaryKey;
}

//MARK: - getter
static inline dispatch_queue_t MMSqliteConcurrentQueue() {
    static dispatch_queue_t queue = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        queue = dispatch_queue_create("com.mmsqlite.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    });
    return queue;
}

@end
